Popular Searches
Popular Course Categories
Popular Courses

Implementing One-to-Many and Many-to-One Relationships

Implementing One-to-Many and Many-to-One Relationships

Hibernate Relationships and Query Language

Implementing One-to-Many and Many-to-One Relationships in Hibernate

Course: Advance Java Training

Topic: Implementing One-to-Many and Many-to-One Relationships

Course Link: Advance Java Training Course

Demo Link: Register for a Free Course Demo

JustAcademy Homepage: Visit JustAcademy


1. Introduction to Relationships in Hibernate

Hibernate is an Object-Relational Mapping (ORM) framework that maps Java objects to relational database tables. In real-world applications, entities frequently need to be associated with other entities. For example, one department can have many employees, while each employee belongs to one department.

Hibernate supports different association types, including one-to-one, one-to-many, many-to-one, and many-to-many relationships. This topic focuses on implementing one-to-many and many-to-one relationships using annotations.

A typical example is a company management system in which a single department manages multiple employees, but each employee is assigned to one department.

2. Learning Objectives

  • Understand one-to-many and many-to-one relationships.
  • Learn how Java entity relationships correspond to relational database tables.
  • Use @OneToMany and @ManyToOne annotations.
  • Configure foreign keys using @JoinColumn.
  • Understand the purpose of mappedBy.
  • Maintain both sides of a bidirectional relationship.
  • Configure cascading operations and orphan removal safely.
  • Persist, retrieve, update, and delete related entities.
  • Identify common relationship-mapping errors.

3. Understanding One-to-Many Relationships

A one-to-many relationship means that one record in a parent table can be associated with multiple records in a child table.

Example: One department can contain multiple employees.

  • One department can have many employees.
  • Each employee is associated with a department in this example.
  • The department is the parent entity.
  • The employee is the child entity.

In Hibernate, the parent collection is commonly mapped using @OneToMany.

@OneToMany(mappedBy = "department")
private List<Employee> employees = new ArrayList<>();

The mappedBy attribute indicates that the relationship is managed by the department field in the Employee entity. It refers to a Java field, not a database column name.

4. Understanding Many-to-One Relationships

A many-to-one relationship means that multiple records in one table can refer to a single record in another table.

Example: Many employees can belong to one department.

  • Multiple employees may reference the same department.
  • Each employee stores a reference to one department.
  • The employee table generally contains the foreign key.
  • The employee's many-to-one association usually owns the relationship.
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;

The @JoinColumn annotation identifies the foreign-key column used to associate an employee with a department.

5. One-to-Many vs. Many-to-One

Feature One-to-Many Many-to-One
Meaning One parent has multiple children. Multiple children refer to one parent.
Typical location Parent entity Child entity
Annotation @OneToMany @ManyToOne
Example field List<Employee> employees Department department
Database foreign key Usually represented through the child-side association Usually represented by a foreign key in the child table
Relationship ownership Inverse side when using mappedBy Usually the owning side

6. Database Structure for the Example

Consider two tables: departments and employees.

DEPARTMENTS
--------------------------------
id          PRIMARY KEY
name

EMPLOYEES
--------------------------------
id          PRIMARY KEY
name
salary
department_id  FOREIGN KEY
              REFERENCES departments(id)

The department_id column in the employee table identifies the department to which an employee belongs.

For example:

Department ID Department Name
1 IT
2 Human Resources
Employee ID Employee Name Department ID
101 Amit 1
102 Neha 1
103 Rahul 2

Amit and Neha belong to the IT department, while Rahul belongs to Human Resources. This illustrates the one-to-many and many-to-one relationship.

7. Setting Up Hibernate

To implement the relationship, create a Java project with Hibernate ORM, a compatible JDBC driver, and a relational database such as MySQL. Configure the database connection and register both entity classes with Hibernate.

For a Maven project, the dependencies typically include Hibernate ORM, the relevant database JDBC driver, and a logging implementation. Use versions compatible with your Java version and project configuration.

A typical project structure is:

src/main/java/
    com/example/entity/
        Department.java
        Employee.java
    com/example/
        Main.java

src/main/resources/
    hibernate.cfg.xml

Configuration requirements include the database URL, username, password, JDBC driver, SQL dialect when needed by the selected Hibernate version, and entity registration. Keep credentials out of source control in real applications.

8. Creating the Department Entity

The Department entity represents a department and contains a collection of employees. The collection is mapped with @OneToMany.

package com.example.entity;

import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "departments")
public class Department {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    @OneToMany(
        mappedBy = "department",
        cascade = CascadeType.ALL,
        orphanRemoval = true
    )
    private List<Employee> employees = new ArrayList<>();

    public Department() {
    }

    public Department(String name) {
        this.name = name;
    }

    public void addEmployee(Employee employee) {
        employees.add(employee);
        employee.setDepartment(this);
    }

    public void removeEmployee(Employee employee) {
        employees.remove(employee);
        employee.setDepartment(null);
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Employee> getEmployees() {
        return employees;
    }
}

Explanation of the Department Entity

  • @Entity marks the class as a persistent entity.
  • @Table(name = "departments") specifies the database table name.
  • @Id marks the primary key.
  • @GeneratedValue configures primary-key generation.
  • @OneToMany declares the collection of related employees.
  • mappedBy = "department" identifies the owning association field in Employee.
  • cascade = CascadeType.ALL propagates supported persistence operations from the department to its employees.
  • orphanRemoval = true allows an employee removed from this collection to be deleted when the change is synchronized, subject to the mapping and persistence context.
  • addEmployee() and removeEmployee() keep both sides of the relationship synchronized.

9. Creating the Employee Entity

The Employee entity represents an employee and contains a reference to the department. The @ManyToOne association normally owns the relationship and determines the foreign-key mapping.

package com.example.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "employees")
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    private double salary;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "department_id")
    private Department department;

    public Employee() {
    }

    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    public Long getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public Department getDepartment() {
        return department;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }
}

Explanation of the Employee Entity

  • @ManyToOne indicates that multiple employee records may reference the same department.
  • fetch = FetchType.LAZY requests that the department association be loaded when needed rather than automatically loaded with every employee query. The exact behavior depends on the provider and how the association is accessed.
  • @JoinColumn(name = "department_id") maps the association to the foreign-key column.
  • The department field is the owning side of this bidirectional relationship.

Version note: This example uses jakarta.persistence imports, as used by modern Hibernate versions. Older Hibernate/JPA projects may use javax.persistence instead. Use the namespace that matches your project's dependencies.

10. Understanding the mappedBy Attribute

The mappedBy attribute tells Hibernate that the one-to-many collection is the inverse side of an association already mapped by the child entity.

@OneToMany(mappedBy = "department")
private List<Employee> employees;

Here, department is the name of the Java field inside Employee. It is not the name of the database column department_id.

Because Employee.department owns the association, updating that field determines which department ID is stored in the employee row.

11. Why Both Sides Must Be Synchronized

In a bidirectional association, Java maintains two references:

  • The department contains a collection of employees.
  • Each employee contains a reference to its department.

Updating only the parent collection is insufficient to change the owning association. For example, adding an employee only to department.getEmployees() without setting employee.setDepartment(department) can leave the in-memory relationship inconsistent and may fail to update the foreign key as intended.

Use a helper method:

Department department = new Department("IT");
Employee employee = new Employee("Amit", 50000);

department.addEmployee(employee);

The helper updates both sides, making the relationship consistent in memory.

12. Persisting a Department and Its Employees

The following example creates a department, adds two employees, and persists the department. With CascadeType.ALL, the persistence operation cascades to the associated employees.

import com.example.entity.Department;
import com.example.entity.Employee;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;

public class Main {
    public static void main(String[] args) {
        Transaction transaction = null;

        try (SessionFactory sessionFactory =
                 HibernateUtil.getSessionFactory();
             Session session = sessionFactory.openSession()) {

            transaction = session.beginTransaction();

            Department department = new Department("IT");

            Employee employee1 =
                new Employee("Amit", 50000);

            Employee employee2 =
                new Employee("Neha", 60000);

            department.addEmployee(employee1);
            department.addEmployee(employee2);

            session.persist(department);

            transaction.commit();

            System.out.println("Department and employees saved.");
        } catch (Exception exception) {
            if (transaction != null && transaction.isActive()) {
                transaction.rollback();
            }
            exception.printStackTrace();
        }
    }
}

Note: HibernateUtil.getSessionFactory() is a project-specific helper that must be implemented to build and provide the configured SessionFactory. In a real application, ensure the factory is managed appropriately rather than unnecessarily created and closed for each operation.

What Happens During Persistence?

  1. A new department object is created.
  2. Two employee objects are created.
  3. The helper method adds each employee to the department and assigns the department reference to each employee.
  4. session.persist(department) makes the department persistent.
  5. The configured cascade propagates persistence to the employees.
  6. Hibernate inserts the department and employee records, setting the employee foreign keys as appropriate.
  7. The transaction commits the database changes.

13. Persisting an Employee for an Existing Department

Sometimes the department already exists and a new employee needs to be assigned to it. Retrieve the department and assign it to the employee within a transaction.

Transaction transaction = session.beginTransaction();

Department department =
    session.find(Department.class, 1L);

if (department != null) {
    Employee employee = new Employee("Rahul", 55000);
    department.addEmployee(employee);
    session.persist(employee);
}

transaction.commit();

This example assumes that the session has been opened and the transaction handling is managed correctly. Since the employee is explicitly persisted, the department does not need to be persisted again. The employee's owning association supplies the foreign-key value.

14. Retrieving a Department and Its Employees

Use find() to retrieve a department by primary key.

Department department =
    session.find(Department.class, 1L);

if (department != null) {
    System.out.println("Department: " + department.getName());

    for (Employee employee : department.getEmployees()) {
        System.out.println(
            employee.getName() + " - " + employee.getSalary()
        );
    }
}

If the employees collection is lazy, access it while the Hibernate session is still open. Accessing an uninitialized lazy collection after its session has closed can cause a LazyInitializationException.

15. Retrieving Employees and Their Department

Because the employee entity contains a many-to-one association, the department can be accessed through the employee.

Employee employee =
    session.find(Employee.class, 101L);

if (employee != null) {
    System.out.println("Employee: " + employee.getName());

    Department department = employee.getDepartment();

    if (department != null) {
        System.out.println(
            "Department: " + department.getName()
        );
    }
}

With lazy fetching, the associated department may be loaded when its properties are accessed. Keep access within the session or explicitly fetch the association when appropriate.

16. Using JOIN FETCH to Load Related Data

When an application needs a department and its employees together, a fetch join can load the relationship in one query.

Department department = session.createQuery(
    "select distinct d from Department d " +
    "left join fetch d.employees where d.id = :id",
    Department.class
)
.setParameter("id", 1L)
.getSingleResult();

The left join fetch retrieves the department and its employees together. The distinct keyword helps express the intention to return unique department results when joining to a collection. Fetch joins should be designed carefully, especially when loading multiple collections or paging results.

17. Understanding Cascade Types

Cascade settings determine which persistence operations are propagated from one entity to associated entities.

Cascade Type Purpose Consideration
PERSIST Propagates persistence to related entities. Useful when new children should be persisted with the parent.
MERGE Propagates merge operations. Useful when associated state should be merged together.
REMOVE Propagates removal operations. Use carefully because associated entities may be deleted.
REFRESH Propagates refresh operations. Reloads associated state from the database.
DETACH Propagates detachment. Removes entities from the current persistence context.
ALL Includes all standard cascade operations. Use only when every included operation is appropriate.

For example:

@OneToMany(
    mappedBy = "department",
    cascade = CascadeType.PERSIST
)
private List<Employee> employees = new ArrayList<>();

Choose cascade behavior according to the lifecycle of the entities. Do not automatically use CascadeType.ALL in every relationship, particularly when child entities may be shared or must outlive their parent.

18. Understanding orphanRemoval

The orphanRemoval attribute is used when a child entity should be removed if it is disassociated from its parent collection. This is appropriate when the child is privately owned by the parent.

@OneToMany(
    mappedBy = "department",
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Employee> employees = new ArrayList<>();

For example, removing an employee from the department collection may cause Hibernate to delete that employee record when the persistence context synchronizes changes with the database.

Important: Orphan removal is different from merely setting the child's department reference to null. Use the helper methods consistently, and do not enable orphan removal when employees must remain independently managed or be reassigned without deletion.

19. Updating an Employee's Department

An employee may be transferred from one department to another. Both collections and the owning reference should be updated in memory.

Department oldDepartment =
    session.find(Department.class, 1L);

Department newDepartment =
    session.find(Department.class, 2L);

Employee employee =
    session.find(Employee.class, 101L);

if (employee != null && newDepartment != null) {
    if (oldDepartment != null) {
        oldDepartment.getEmployees().remove(employee);
    }

    employee.setDepartment(newDepartment);
    newDepartment.getEmployees().add(employee);
}

transaction.commit();

This basic example demonstrates synchronization of both sides. If the old department uses orphanRemoval = true, removing the employee from its collection can mark it for deletion. For transfers, avoid using orphan removal if the intended behavior is to move a child between parents, or implement a carefully designed transfer operation and verify the mapping and Hibernate lifecycle behavior.

20. Deleting a Department

Deleting a parent entity requires care when child records reference it through a foreign key. The appropriate approach depends on the cascade configuration, orphan-removal setting, and database constraints.

Transaction transaction = session.beginTransaction();

Department department =
    session.find(Department.class, 1L);

if (department != null) {
    session.remove(department);
}

transaction.commit();

If CascadeType.REMOVE or CascadeType.ALL is configured, removal can cascade to employees. Without an appropriate cascade, deletion may fail because employees still reference the department. Before choosing a deletion strategy, confirm whether the business rules require deleting employees, reassigning them, or preventing department deletion.

21. Unidirectional and Bidirectional Relationships

Feature Unidirectional Bidirectional
Navigation One entity navigates to the other. Both entities navigate to each other.
Example Department stores employees, but Employee does not reference Department. Department stores employees and Employee references Department.
Mapping complexity Often simpler at the object-model level. Requires consistent management of both sides.
Foreign-key mapping Depends on the selected mapping strategy. Often uses the child-side many-to-one foreign key.
Typical use When navigation is required in only one direction. When both parent-to-child and child-to-parent navigation are needed.

A bidirectional relationship is not automatically better. Choose the mapping that matches how the application needs to access and manage the data.

22. Common Errors and Troubleshooting

Problem Possible Cause Solution
Foreign key remains null The owning side was not assigned. Set the employee's department reference before persistence or update.
mappedBy error The value does not match the child entity's Java field name. Use the exact field name, such as department.
LazyInitializationException A lazy collection or association was accessed after its session closed. Load required data within the transaction or use a suitable fetch query.
Foreign-key constraint violation A child references a missing parent, or a referenced parent is deleted prematurely. Persist or retrieve the parent correctly and follow the intended deletion rules.
Unexpected child deletion Cascade remove or orphan removal is configured inappropriately. Review lifecycle requirements and cascade settings.
Duplicate or unexpected SQL updates Both sides are not synchronized or the mapping is unnecessarily complex. Use helper methods and a clear owning-side mapping.
Multiple queries while iterating Lazy loading causes an N+1 query problem. Use a fetch join, entity graph, or suitable batch-fetch strategy where appropriate.

23. Best Practices

  • Use @ManyToOne on the child entity for the common foreign-key-based mapping.
  • Use @OneToMany(mappedBy = "...") on the parent for a bidirectional relationship.
  • Keep the owning side and inverse collection synchronized through helper methods.
  • Initialize collection fields, for example with new ArrayList<>().
  • Choose cascade operations according to entity ownership and lifecycle requirements.
  • Use orphanRemoval = true only when removing a child from the parent truly means deleting it.
  • Prefer explicit fetch plans for queries that require related entities.
  • Avoid accessing lazy associations outside an open persistence context.
  • Use transactions for database modifications and roll back after failures.
  • Use appropriate validation, foreign-key constraints, and database indexes.
  • Be cautious when serializing bidirectional entities to JSON, as circular references can cause infinite recursion.
  • Use DTOs or explicit query projections when exposing relationship data through APIs.

24. Practical Exercise

Project: Department and Employee Management System.

Create a Hibernate application that stores departments and employees using a one-to-many and many-to-one relationship.

Requirements

  1. Create a Department entity with an ID, name, and employee collection.
  2. Create an Employee entity with an ID, name, salary, and department reference.
  3. Map the relationship using @OneToMany and @ManyToOne.
  4. Configure the employee foreign key using @JoinColumn.
  5. Insert a department with at least three employees.
  6. Retrieve a department and display its employees.
  7. Retrieve an employee and display the associated department.
  8. Update an employee's department and verify the foreign key.
  9. Test the configured cascade behavior.
  10. Test deletion behavior in a safe development database.

Expected Outcome

The application should correctly store and retrieve related records, maintain consistent entity references, and demonstrate how Hibernate synchronizes Java entity relationships with database foreign keys.

25. Interview Questions and Answers

Q1. What is a one-to-many relationship?

A one-to-many relationship allows one parent entity to be associated with multiple child entities. For example, a department may contain several employees.

Q2. What is a many-to-one relationship?

A many-to-one relationship allows multiple child entities to reference one parent entity. For example, several employees may belong to one department.

Q3. What is the purpose of @OneToMany?

@OneToMany maps a parent entity to a collection of associated child entities.

Q4. What is the purpose of @ManyToOne?

@ManyToOne maps a child entity to a single associated parent entity. It commonly represents the side that holds the foreign-key mapping.

Q5. What does mappedBy mean?

mappedBy identifies the Java association field that owns the relationship on the other entity. It avoids independently mapping the same bidirectional relationship from the inverse side.

Q6. What is the owning side of a relationship?

The owning side is the side responsible for mapping and updating the relationship in the database. In the example, Employee.department is the owning side.

Q7. What is the purpose of @JoinColumn?

@JoinColumn configures the database join column, commonly the foreign-key column used by an association.

Q8. What is the difference between cascade and orphan removal?

Cascade propagates specified persistence operations to associated entities. Orphan removal allows a privately owned child to be deleted when it is disassociated from its parent collection.

Q9. Why should both sides of a bidirectional relationship be updated?

Updating both sides keeps the Java object graph consistent. The owning side must be updated so that the intended foreign-key relationship is persisted.

Q10. What is LazyInitializationException?

It commonly occurs when an uninitialized lazy association is accessed after the Hibernate session or persistence context needed to load it has closed.

26. Quick Revision

  • @OneToMany: one parent is associated with multiple children.
  • @ManyToOne: multiple children may reference one parent.
  • @JoinColumn: configures the join or foreign-key column.
  • mappedBy: points to the owning association's Java field.
  • The child-side many-to-one association commonly owns the foreign-key mapping.
  • Helper methods can keep both sides of a bidirectional relationship synchronized.
  • Cascade settings control propagation of persistence operations.
  • Orphan removal is appropriate only when the parent owns the child's lifecycle.
  • Lazy loading and fetch joins should be planned according to application requirements.
  • Transactions, foreign-key constraints, and correct lifecycle rules help maintain data integrity.

27. Conclusion

Implementing one-to-many and many-to-one relationships is a fundamental skill in Hibernate. These mappings allow applications to represent associations between parent and child entities while maintaining relational database integrity. By using @OneToMany, @ManyToOne, @JoinColumn, and mappedBy correctly, developers can build reliable persistence layers.

For practical learning, create the Department and Employee example, test insert and retrieval operations, and examine how Hibernate manages foreign keys, cascading, lazy loading, and transactions.

28. Additional Learning Resources

Previous Next
whatsapp